/*
 * Package il.ac.biu.cs.grossmm.impl.simple
 * File SipAdapter.java
 * 
 * This is a part of presence service framework prototype. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package il.ac.biu.cs.grossmm.impl.simple;

/*
 * To-do list
 * ==========
 * 
 * TODO: OPTIONS method to allow determining capabilities of the server
 *  
 */

import il.ac.biu.cs.grossmm.api.server.Component;
import il.ac.biu.cs.grossmm.api.server.ComponentManager;
import il.ac.biu.cs.grossmm.api.simple.RequestProcessException;
import il.ac.biu.cs.grossmm.api.simple.SipRequestProcessor;
import il.ac.biu.cs.grossmm.api.simple.SipSharedProvider;

import java.text.ParseException;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.TooManyListenersException;

import javax.sip.*;
import javax.sip.header.CallIdHeader;
import javax.sip.header.Header;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;

import org.apache.log4j.Logger;

/**
 * SipAdapter
 */
public class SipAdapter implements SipListener, SipSharedProvider, Component {

    // Log4j
    Logger logger = Logger.getLogger(SipAdapter.class);

    // SIP
    SipFactory sipFactory = null;

    SipStack sipStack = null;

    private ListeningPoint listeningPoint;

    private SipProvider provider;

    private MessageFactory messageFactory;

    private static final int PORT = 5004;

    private ServerTransaction serverTransaction;

    private Request request;

    // Listeners
    List<SipRequestProcessor> listeners = new LinkedList<SipRequestProcessor>();

    
    public void doLoad() throws Exception {
        if (logger.isDebugEnabled()) {
            logger.debug("doLoad() - start");
        }

        setup();


        if (logger.isDebugEnabled()) {
            logger.debug("doLoad() - end");
        }
    }

    public void doUnload() {
        if (logger.isDebugEnabled()) {
            logger.debug("doUnload() - start");
        }

        destroy();

        if (logger.isDebugEnabled()) {
            logger.debug("doUnload() - end");
        }
    }

    /**
     * Initializes SIP stack, SIP provider and related factories
     * 
     * @throws javax.sip.PeerUnavailableException
     * @throws javax.sip.TransportNotSupportedException
     * @throws javax.sip.InvalidArgumentException
     * @throws javax.sip.ObjectInUseException
     * @throws java.util.TooManyListenersException
     */
    private void setup() throws PeerUnavailableException,
            TransportNotSupportedException, InvalidArgumentException,
            ObjectInUseException, TooManyListenersException {
        if (logger.isDebugEnabled()) {
            logger.debug("setup() - start");
        }

        sipFactory = SipFactory.getInstance();
        sipFactory.setPathName("gov.nist");

        logger.debug("Obtained Sip Factory");

        Properties props = new Properties();

        props.setProperty("javax.sip.IP_ADDRESS", "127.0.0.1");
        props.setProperty("javax.sip.STACK_NAME", "TestStack");

        // props.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "32");

        // props.setProperty("javax.sip.OUTBOUND_PROXY", "");
        // props.setProperty("javax.sip.ROUTER_PATH", "");
        // props.setProperty("javax.sip.EXTENSION_METHODS", "");
        // props.setProperty("javax.sip.RETRANSMISSON_FILTER", "");

        sipStack = sipFactory.createSipStack(props);
        messageFactory = sipFactory.createMessageFactory();

        logger.debug("Created stack: " + sipStack);

        listeningPoint = sipStack.createListeningPoint(PORT, "udp");

        logger.debug("Listening Point: " + listeningPoint + " PORT: " + PORT);

        provider = sipStack.createSipProvider(listeningPoint);

        logger.debug("Provider: " + provider);

        provider.addSipListener(this);

        if (logger.isDebugEnabled()) {
            logger.debug("setup() - end");
        }
    }

    /**
     * Termenate SIP stack
     */
    public void destroy() {
        if (logger.isDebugEnabled()) {
            logger.debug("destroy() - start");
        }

        try {
            logger.info("Terminating SIP stack");
            if (sipStack != null) {
                if (provider != null) {
                    provider.removeSipListener(this);
                    sipStack.deleteSipProvider(provider);
                }

                if (listeningPoint != null)
                    sipStack.deleteListeningPoint(listeningPoint);
            }

            logger.info("SIP stack terminated");
        } catch (ObjectInUseException e) {
            logger.fatal(e.getMessage(), e);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("destroy() - end");
        }
    }

    public void processRequest(RequestEvent requestEvent) {
        if (logger.isDebugEnabled()) {
            logger.debug("processRequest() - start");
        }

        request = requestEvent.getRequest();
        serverTransaction = requestEvent.getServerTransaction();

        // iterate all listerens until one of them processes the request
        try {
            for (SipRequestProcessor listener : listeners) {
                Response response = listener.processRequest(requestEvent);

                if (response != null) { 
                    logger.debug("Got response to send. Value of serverTransaction: " + serverTransaction);
                    if( response != SipRequestProcessor.PROCESSED ) 
	                    if (serverTransaction == null) 
	                        provider.sendResponse(response);
	                    else
	                        serverTransaction.sendResponse(response);

                    return;
                }
            }

            // no listener can process this request

            
            ServerTransaction st = requestEvent.getServerTransaction();
            Response response = messageFactory.createResponse(Response.METHOD_NOT_ALLOWED,
                    requestEvent.getRequest());
            if( st == null )
                provider.sendResponse(response);
            else
                st.sendResponse(response);
            
        } catch (SipException e) {
            logger.fatal("Cannot send response", e);
        } catch (ParseException e) {
            logger.fatal("Cannot send response", e);
        } catch (InvalidArgumentException e) {
        	logger.fatal("Cannot send response", e);
		} finally {
            request = null;
            
            if (logger.isDebugEnabled()) {
                logger.debug("processRequest(RequestEvent) - end");
            }
        }
    }

    public void processResponse(ResponseEvent responseEvent) {
        if (logger.isDebugEnabled()) {
            logger.debug("processResponse() - start");
        }

        // iterate all listerens until one of them processes the response
        for (SipRequestProcessor listener : listeners) {
            if (listener.processResponse(responseEvent))
                return;
        }

        logger.info("Response not handeled " + responseEvent.getResponse());
        // TODO: should we do anything if response is not handeled?
        
        if (logger.isDebugEnabled()) {
            logger.debug("processResponse() - end");
        }
    }

    public void processTimeout(TimeoutEvent timeoutEvent) {
        if (logger.isDebugEnabled()) {
            logger.debug("processTimeout() - start");
        }

        // iterate all listerens until one of them processes the timeout
        for (SipRequestProcessor listener : listeners) {
            if (listener.processTimeout(timeoutEvent))
                return;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("processTimeout(TimeoutEvent) - end");
        }
    }

    // ////////////////////////////////////////////////

    // //// methods defined in ISipSharedProvider /////

    public SipProvider getProvider() {
        return provider;
    }

    public void respond(int code, String reason, Request request,
            ServerTransaction transaction) {
        if (logger.isDebugEnabled()) {
            logger.debug("respond() - start");
        }

        try {
            logger.debug("responding with " + code);
            Response response = messageFactory.createResponse(code, request);
            if (reason != null)
                response.setReasonPhrase(reason);

            if (transaction == null)
                provider.sendResponse(response);
            else
                transaction.sendResponse(response);
        } catch (ParseException e) {
            logger.fatal(e.getMessage(), e);
        } catch (SipException e) {
            logger.fatal(e.getMessage(), e);
        } catch (InvalidArgumentException e) {
        	logger.fatal(e.getMessage(), e);
		}

        if (logger.isDebugEnabled()) {
            logger
                    .debug("respond(int, String, Request, ServerTransaction) - end");
        }
    }

    public void addSipListener(SipRequestProcessor listener) {
        if (logger.isDebugEnabled()) {
            logger.debug("addSipListener() - start");
        }

        listeners.add(listener);

        if (logger.isDebugEnabled()) {
            logger.debug("addSipListener(ISipListinerCandidate) - end");
        }
    }

    public void removeSipListener(SipRequestProcessor listener) {
        if (logger.isDebugEnabled()) {
            logger.debug("removeSipListener() - start");
        }

        listeners.remove(listener);

        if (logger.isDebugEnabled()) {
            logger.debug("removeSipListener(ISipListinerCandidate) - end");
        }
    }

    public SipStack getSipStack() {
        return sipStack;
    }

    public CallIdHeader getNewCallId() {
        if (logger.isDebugEnabled()) {
            logger.debug("getNewCallId() - start");
        }

        CallIdHeader returnCallIdHeader = provider.getNewCallId();
        if (logger.isDebugEnabled()) {
            logger.debug("getNewCallId() - end");
        }
        return returnCallIdHeader;
    }

    public ClientTransaction getNewClientTransaction(Request request)
            throws TransactionUnavailableException {
        if (logger.isDebugEnabled()) {
            logger.debug("getNewClientTransaction() - start");
        }

        ClientTransaction returnClientTransaction = provider
                .getNewClientTransaction(request);
        if (logger.isDebugEnabled()) {
            logger.debug("getNewClientTransaction(Request) - end");
        }
        return returnClientTransaction;
    }

    public void sendRequest(Request request) throws SipException {
        if (logger.isDebugEnabled()) {
            logger.debug("sendRequest() - start");
        }

        provider.sendRequest(request);

        if (logger.isDebugEnabled()) {
            logger.debug("sendRequest(Request) - end");
        }
    }

    public void sendResponse(Response response) throws SipException {
        if (logger.isDebugEnabled()) {
            logger.debug("sendResponse() - start");
        }

        provider.sendResponse(response);

        if (logger.isDebugEnabled()) {
            logger.debug("sendResponse(Response) - end");
        }
    }

    public ServerTransaction startServerTransaction() {
        if (logger.isDebugEnabled()) {
            logger.debug("startServerTransaction() - start");
        }

        if (request == null) { // improper method call?
            logger.error("request == null in startServerTransaction");
            return null;
        }

        try {

            if (logger.isDebugEnabled()) {
                logger.debug("Value of serverTransaction: " + serverTransaction);
            }
            
            if (serverTransaction == null) {
                serverTransaction = provider.getNewServerTransaction(request);
            }

        } catch (TransactionAlreadyExistsException e) {
            logger.error("Cannot create a new server transaction", e);
        } catch (TransactionUnavailableException e) {
            logger.error(
                    "Cannot create a new server transaction (unavailable)", e);
        }

        if (logger.isDebugEnabled()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Return value: " + serverTransaction);
            }
            logger.debug("startServerTransaction() - end");
        }
        return serverTransaction;
    }

    public Response createResponse(int code, String reason) {
        if (logger.isDebugEnabled()) {
            logger.debug("createResponse() - start");
        }

        if (request == null) // improper method call?
            return null;

        try {
            Response respose = messageFactory.createResponse(code, request);

            if (reason != null)
                respose.setReasonPhrase(reason);

            if (logger.isDebugEnabled()) {
                logger.debug("createResponse(int, String) - end");
            }
            return respose;
        } catch (ParseException e) {
            logger.error("createResponse(int, String)", e);

            logger.error("Cannot create response", e);
            return null;
        }
    }

    public Response createResponse(RequestProcessException exception) {
        if (logger.isDebugEnabled()) {
            logger.debug("createResponse() - start");
        }

        Response response = createResponse(exception.getCode(), exception
                .getReason());
        if (response == null) { // das ist fantastisch

            if (logger.isDebugEnabled()) {
                logger.debug("createResponse(RequestProcessException) - end");
            }
            return null;
        }

        List<Header> headers = exception.getHeaders();
        if (headers != null)
            for (Header header : headers)
                response.setHeader(header);

        if (logger.isDebugEnabled()) {
            logger.debug("createResponse(RequestProcessException) - end");
        }
        return null;
    }

	public void aborted(Object hint, Exception e)
	{
		destroy();
	}

	public void setComponentManager(ComponentManager container) throws Exception
	{
		setup();
		// TODO make a service...
	}

	public void processDialogTerminated(DialogTerminatedEvent arg0) {
		logger.warn("processDialogTerminated");
	}

	public void processIOException(IOExceptionEvent arg0) {
		logger.warn("processIOException");

	}

	public void processTransactionTerminated(TransactionTerminatedEvent arg0) {
		logger.warn("processTransactionTerminated");
	}

}
